/*
 * Class Engine
 *
 * Version 1.0
 *
 * Thursday, August 14, 2008
 *
 * Created by Palidino76
 */

package net.com.hydroPK;

import net.com.hydroPK.io.ActionSender;
import net.com.hydroPK.io.Packets;
import net.com.hydroPK.model.Player;
import net.com.hydroPK.model.PlayerFollow;
import net.com.hydroPK.model.items.PlayerBank;
import net.com.hydroPK.model.items.PlayerItems;
import net.com.hydroPK.model.update.PlayerMovement;
import net.com.hydroPK.model.update.PlayerUpdate;
import net.com.hydroPK.net.codec.RS2LoginProtocol;
import net.com.hydroPK.npcs.NPC;
import net.com.hydroPK.npcs.SmartNPC;
import net.com.hydroPK.npcs.NPCDrops;
import net.com.hydroPK.npcs.loading.LoadNPCLists;
import net.com.hydroPK.npcs.loading.LoadSmartNPC;
import net.com.hydroPK.npcs.loading.NPCList;
import net.com.hydroPK.npcs.update.NPCMovement;
import net.com.hydroPK.npcs.update.NPCUpdate;
import net.com.hydroPK.util.Misc;
import net.com.hydroPK.world.ObjectList;
import net.com.hydroPK.world.items.Items;
import net.com.hydroPK.world.mapdata.MapData;
import net.com.hydroPK.content.BountyHunter;
import net.com.hydroPK.world.geLoader;
import net.com.hydroPK.io.FileManager;
import net.com.hydroPK.clanchat.*;
import net.com.hydroPK.content.*;

import javax.swing.*;
import java.net.Socket;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class Engine implements Runnable {
    /**
	* potion handler by mad turnip
	**/

	public static PotionHandler potionHandler = new PotionHandler();
    public static ClanChat clanChat = new ClanChat();
    public static FileManager fileManager = new FileManager();
    public static geLoader geLoader = new geLoader();
    public static int id;

    public static boolean pickup = false;
    static String text;
    /**
     * Update int.
     */
    public static int updateTime = 0;
    /**
     * PlayerFollowing.
     */
    public static PlayerFollow playerFollow = new PlayerFollow();

    /**
     * Loads and stores map data for map regions.
     */
    public static MapData mapData = new MapData();
 public static BountyHunter BountyHunter = new BountyHunter();

    /**
     * Max players allowed.
     */
    public static int maxPlayers = 300;
    /**
     * When a new player logs in, a new player is created and stored in this array.
     * <p>The server cannot exceed 2000 players.
     * The id 0 cannot be used as it is not handled by the client corrently.
     */
    public static Player[] players = new Player[2000];

    /**
     * Max id an NPC can have loaded out of the npclist config.
     */
    public static int maxListedNPCs = 10000;
    /**
     * When a new NPC is created, it is stored in this array.
     * <p>There can not be more than 5000 NPCs spawned at a time.
     * The id 0 cannot be used as it is not handled by the client corrently.
     */
    public static NPC[] npcs = new NPC[50000];
    public static ObjectList[] ObjectList = new ObjectList[2000];
    /**
     * Contains NPC data such as respawn times, names, examines, etc.
     */

    public static int maxSN = 1000;
    public static SmartNPC[] SN = new SmartNPC[maxSN];

    public static NPCList[] npcLists = new NPCList[maxListedNPCs];
    /**
     * The class thread.
     */
    private Thread engineThread = new Thread(this);
    /**
     * Set true if the engine should run.
     */
    private boolean engineRunning = true;
    /**
     * Handles packets recieved from the server.
     */
    public static Packets packets = new Packets();
    /**
     * Contains a method for every known frame.
     */
    //public static geLoader geLoader = new geLoader();
    //public static GrandExchangeLoader GrandExchangeLoader;
    public static ActionSender actionSender = new ActionSender();
    /**
     * Handles player movement.
     */
    public static PlayerMovement playerMovement = new PlayerMovement();
    /**
     * Contains all item information.
     */
    public static Items items;
    /**
     * Handles doors/door clicks
     */
    public static DoorHandler doorHandler = new DoorHandler();
    /**
     * Handles player updates.
     */
    public static PlayerUpdate playerUpdate = new PlayerUpdate();
    /**
     * Handles NPC movement.
     */
    public static NPCMovement npcMovement = new NPCMovement();
    /**
     * Handles NPC updates.
     */
    public static NPCUpdate npcUpdate = new NPCUpdate();
    /**
     * Handles players items.
     */
    public static PlayerItems playerItems = new PlayerItems();
    public static PlayerBank playerBank = new PlayerBank();
    /**
     * Handles thread
     */
    public Thread neccesaryThread;

    /**
     * Constructs a new Engine and loads item and NPC data from configs.
     */
    public Engine() {

        // Npc Drops
        for (int i = 0; i < 100000; i++) {
            NpcDrops[i] = null;
        }
        loadNPCDrops();

        LoadSmartNPC LSN = new LoadSmartNPC();
        items = new Items(this);
        LoadNPCLists lnl = new LoadNPCLists();
        lnl = null;
        engineThread.start();
    }

    /**
     * Returns 4 coordinates to get wilderness coords.
     */
    public static boolean inWilderness(Player p) {
        return p.absX > 1977 && p.absX < 1970 && p.absY > 5008 && p.absY < 4996;
    }

    public static void newObject(int object, int height, int x, int y, int face, int type, int decay, int returnId) {
        ObjectList[newObjectSlot()] = new ObjectList(object, height, x, y, face, type, decay, returnId);
    }
	
    public static int newObjectSlot() {
        for (int i = 1; i < ObjectList.length; i++) {
            if (ObjectList[i] == null || ObjectList[i].objectId == -1) {
                return i;
            }
        }
        return 2000;
    }


    /**
     * The thread's process.
     * <p>This processes at a rate of 100 milliseconds, and the processing time
     * is subtracted from the amount of time it sleeps to keep at a rate of 100
     * milliseonds. Packets are checked every 100 milliseconds for faster response time,
     * and most other updates are processed every 600 milliseconds.
     */
    public void run() {
        //addStaticObject(id, height, x, y, face, type);

        long lastEntityUpdate = 0;
        long lastEntityUpdate2 = 0;
        long curTime;
        //Startup();
        while (engineRunning) {
            curTime = System.currentTimeMillis();
            connectAwaitingPlayers();
            packets.parseIncomingPackets();
            if (curTime - lastEntityUpdate2 >= 1000) {
                for (Player p : players) {
                    if (p == null || !p.online) {
                        continue;
                    }
                    p.processTimers();
                }
                lastEntityUpdate2 = curTime;
            }
            if (curTime - lastEntityUpdate >= 600) {
                if (updateTime > 0) {
                    updateTime--;
                }
                if (updateTime == 0) {
                    try {
                        //Server.GrandExchangeLoader.saveOffers();
                    } catch (Exception e) {
                    }
                    //System.exit(0);
                }
                items.process();
                for (Player p : players) {
                    // Proccess and player movement, removing disconnected players.
                    if (p == null || !p.online) {
                        continue;
                    }
                    if (p.disconnected[0] && p.disconnected[1]) {
                        removePlayer(p.playerId);
                        continue;
                    }
                    p.process();
                    playerMovement.getNextPlayerMovement(p);
                }
                for (Player p : players) {
                    // Update players.
                    if (p == null || !p.online) {
                        continue;
                    }
                    playerUpdate.update(p);
                }
                for (Player p : players) {
                    // Reset masks and send bytes.
                    if (p == null || !p.online) {
                        continue;
                    }
                    playerUpdate.clearUpdateReqs(p);
                    Server.socketListener.writeBuffer(p);
                }
                for (NPC n : npcs) {
                    if (n == null) {
                        continue;
                    }
                    npcUpdate.clearUpdateMasks(n);
                }
                for (NPC n : npcs) {
                    if (n == null) {
                        continue;
                    }
                    n.process();
                    if (!n.isDead) {
                        if (n.randomWalk) {
                            npcMovement.randomWalk(n);
                        }
                    } else {
                        if (!n.deadEmoteDone) {
                            n.deadEmoteDone = true;
                            n.combatDelay = n.getDeathDelay1();
                        } else if (n.deadEmoteDone && !n.hiddenNPC && n.combatDelay <= 0) {
                            n.hiddenNPC = true;
                        } else if (n.hiddenNPC && n.respawnDelay <= 0) {
                            npcs[n.npcId] = null;
                            rebuildNPCs();
                            if (n.needsRespawn) {
                                newNPC(n.npcType, n.makeX, n.makeY, n.heightLevel, n.moveRangeX1, n.moveRangeY1,
                                        n.moveRangeX2, n.moveRangeY2, true, 0);
                            }

                        }
                    }
                    lastEntityUpdate = curTime;
                }
            }
            try {
                Thread.sleep(100 - (System.currentTimeMillis() - curTime));
            } catch (Exception e) {
            }
        }
    }

    /**
     * Assign a player to the connection.
     * <p>When a new connection is recieved, a new Player class is created which waits for the run method
     * to run the Login class to finish the connection. If the id is 0, or the server is full, no
     * connection is made and the socket is closed.
     *
     * @param socket The socket the new player is connected to.
     * @param id     The id which the new player will be created at.
     */
    public void addConnection(Socket socket, int id) {
        if (id == 0) {
            Server.socketListener.removeSocket(id);
            return;
        }
        players[id] = new Player(socket, id);
    }

    /**
     * Run the login class to finish a new connection.
     * <p>Loops through all the players looking for any that are not online.
     * Because the socket can't cause the server to process slower, there
     * are multiple login stages, and the player will go through the login class
     * two or three times before fully logging in.
     */
    public void connectAwaitingPlayers() {
        RS2LoginProtocol login = null;
        for (Player p : players) {
            if (p == null || p.online) {
                continue;
            }
            if (login == null)
                login = new RS2LoginProtocol();
            login.login(p);
            if (!p.online && p.loginStage == -1 || (System.currentTimeMillis() - p.loginTimeout) >= 15000) {
                /*
                 * Remove the player if he failed to connect or it was the update server.
                 */
                removePlayer(p.playerId);
            }
        }
    }


    /**
     * Discard a player with the specified id in the player array.
     * <p>This method should only be called when the player is ready to be removed.
     * This nulls closes the players socket and then nulls the player.
     */
    public static void removePlayerr(String s) {
        try {
            if (players[id].online) {
                players[id].saveCharacter();
            }
        } catch (Exception e) {
            players[id].destruct();
            players[id] = null;
            Server.socketListener.removeSocket(id);
        }
        //if (players[id].online) {
        //    try {
        //        players[id].saveCharacter();
        //    } catch (Exception e) {
        //    }
        //}
        players[id].destruct();
        players[id] = null;
        Server.socketListener.removeSocket(id);
    }

    /**
     * Discard a player with the specified id in the player array.
     * <p>This method should only be called when the player is ready to be removed.
     * This nulls closes the players socket and then nulls the player.
     *
     * @param id The position in the player array that should be removed.
     */
    public void removePlayer(int id) {
        try {
            if (players[id].online) {
                players[id].saveCharacter();
            }
        } catch (Exception e) {
            players[id].destruct();
            players[id] = null;
            Server.socketListener.removeSocket(id);
        }
        //if (players[id].online) {
        //    try {
        //        players[id].saveCharacter();
        //    } catch (Exception e) {
        //    }
        //}
        players[id].destruct();
        players[id] = null;
        Server.socketListener.removeSocket(id);
    }

    /**
     * Check player count.
     */
    public static int getPlayerCount() {
        int count = 0;

        for (Player p : players) {
            if (p != null) {
                count++;
            }
        }
        return count;
    }

    /**
     * This method tells every player to re-add any NPCs within distance.
     * <p>By calling this method, with the next NPC update it will discard the NPC list and
     * re-loop through every NPC and re-add them if they fit the specifications.
     */
    public void rebuildNPCs() {
        for (Player p : players) {
            if (p == null) {
                continue;
            }
            p.rebuildNPCList = true;
        }
    }

    /**
     * Returns a players id based on their username, or 0 if the player is not online.
     * <p>This will check if a player is online based on searching every characters username,
     * and comparing it to playerName. Because index 0 is not used and won't throw an
     * ArrayoutofBounds exception error, 0 is returned if the username isn't found.
     *
     * @param playerName The name to compare with the other online player names.
     * @return Returns the index of the player with that username, or 0 if the username isn't found.
     */
    public static int getIdFromName(String playerName) {
        playerName.replaceAll("_", " ");
        for (Player p : players) {
            if (p == null) {
                continue;
            }
            if (p.username.equalsIgnoreCase(playerName)) {
                return p.playerId;
            }
        }
        return 0;
    }

    public int getNPCIndex(int NPC) {
        for (SmartNPC s : SN) {
            if (NPC == s.ID) {
                return s.index;
            }
        }
        return -1;
    }

    /**
     * Returns the description of npcId.
     *
     * @param npcId The NPC to get the examine for.
     * @return Returns the examine.
     */
    public String getNPCDescription(int npcId) {
        if (npcId == -1 || npcId >= maxListedNPCs) {
            return new String("An NPC.");
        }
        if (npcLists[npcId] != null) {
            return (npcLists[npcId].examine);
        }
        return "NPC " + npcId;
    }

    public String getNPCName(int npcType) {
        if (npcType > maxListedNPCs) {
            return "NPCType = " + npcType;
        } else if (npcLists[npcType] != null) {
            return npcLists[npcType].npcName;
        } else {
            return "NPCType = " + npcType;
        }
    }

    public static NPCDrops NpcDrops[] = new NPCDrops[100000];
    public static double[][] drops = new double[90000][100]; // 15 drops per NPC
    public static int[] dropCount = new int[90000];

    public static void dropNPCItems(int NPCID, NPC npc, Player killer) {
        try {
            int totalDrops = dropCount[NPCID] / 3;
            if (totalDrops > 0) {
                // Random roller = new Random();
                for (int i = 0; i < dropCount[NPCID]; i += 3) {
                    double roll = Math.random() * 100;
                    if (roll <= drops[NPCID][i + 2]) {
                        if ((drops[NPCID] != null) && (npc != null))
                            items.createGroundItem((int) drops[NPCID][i], (int) drops[NPCID][i + 1], npc.absX,
                                    npc.absY, npc.heightLevel, killer.username);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
    public static int GetNPCDropArrayID(int NPCID, int DropType) {
        for (int i = 0; i < 100000; i++) {
            if (NpcDrops[i] != null) {
                if ((NpcDrops[i].npcId == NPCID)
                        && (NpcDrops[i].DropType == DropType)) {
                    return i;
                }
            }
        }
        return -1;
    }
    public void loadNPCDrops() {
        String line = "";
        String token = "";
        String token2 = "";
        String token2_2 = "";
        String[] token3 = new String[10];
        boolean EndOfFile = false;
        BufferedReader characterfile = null;
        try {
            characterfile = new BufferedReader(new FileReader("./data/npcs/npcdrops.cfg"));
        } catch (FileNotFoundException fileex) {
            System.out.println("[LOAD NPC DROPS] File not found.");
            return;
        }
        try {
            line = characterfile.readLine();
        } catch (IOException ioexception) {
            System.out.println("[LOAD NPC DROPS] Error loading file.");
            return;
        }
        while ((EndOfFile == false) && (line != null)) {
            line = line.trim();
            int spot = line.indexOf("=");
            if (spot > -1) {
                token = line.substring(0, spot);
                token = token.trim();
                token2 = line.substring(spot + 1);
                token2 = token2.trim();
                token2_2 = token2.replaceAll("\t\t", "\t");
                token2_2 = token2_2.replaceAll("\t\t", "\t");
                token2_2 = token2_2.replaceAll("\t\t", "\t");
                token2_2 = token2_2.replaceAll("\t\t", "\t");
                token2_2 = token2_2.replaceAll("\t\t", "\t");
                token3 = token2_2.split("\t");
                if (token.equals("npcdrop")) {
                    if (Integer.parseInt(token3[0]) <= -1)
                        continue;
                    drops[Integer.parseInt(token3[0])][dropCount[Integer.parseInt(token3[0])]] = Integer
                            .parseInt(token3[1]);
                    drops[Integer.parseInt(token3[0])][dropCount[Integer
                            .parseInt(token3[0])] + 1] = Integer
                            .parseInt(token3[2]);
                    drops[Integer.parseInt(token3[0])][dropCount[Integer
                            .parseInt(token3[0])] + 2] = Integer
                            .parseInt(token3[3]);
                    dropCount[Integer.parseInt(token3[0])] += 3;
                }
            }
            try {
                line = characterfile.readLine();
            } catch (IOException ioexception1) {
                EndOfFile = true;
            }
        }
        try {
            characterfile.close();
        } catch (IOException ioexception) { }
    }

    /**
     * Spawn a new NPC.
     * <p>This will create a new visible NPC, with the default values from its list and the parameters.
     *
     * @param type         The type of NPC this is, such as 50 for the King black dragon.
     * @param absX         The absolute x coordinate to spawn the NPC.
     * @param absY         The absolute y coordinate to spawn the NPC.
     * @param height       The height level to set the NPC.
     * @param mRX1         The distance it can walk west; must be lower than absX to have any effect.
     * @param mRY1         The distance it can walk south; must be lower than absY to have any effect.
     * @param mRX2         The distance it can walk east; must be higher than absX to have any effect.
     * @param mRY2         The distance it can walk north; must be higher than absY to have any effect.
     * @param needsRespawn Set to true if the NPC should respawn after it dies.
     * @return Returns the index the NPC was placed at.
     */
    public static int newNPC(int type, int absX, int absY, int height, int mRX1, int mRY1, int mRX2, int mRY2, boolean needsRespawn, int playerIndex) {
        int index = -1;
        for (int i = 1; i < npcs.length; i++) {
            if (npcs[i] == null) {
                index = i;
                break;
            }
        }
        if (index == -1) {
            Misc.println("Max number of NPCs spawned.");
            return -1;
        }
        NPC n = npcs[index] = new NPC(type, index);
        if (n.currentHP <= 0) {
            n.currentHP = 100;
        }
        if (type == 7771) {
            n.brokenArmour = 8;
        }
        n.absX = absX;
        n.absY = absY;
        n.makeX = absX;
        n.makeY = absY;
        n.heightLevel = height;
        n.moveRangeX1 = mRX1;
        n.moveRangeY1 = mRY1;
        n.moveRangeX2 = mRX2;
        n.moveRangeY2 = mRY2;
        n.needsRespawn = needsRespawn;
        for (int i = 0; i < n.summoningMonsters.length; i++) {
            if (type == 4284 || type == n.summoningMonsters[i])
                n.spawnedFor = playerIndex;
        }
        if (type == 4284)
            n.underAttack = true;
        if (type == 4284)
            n.requestText("YES! I'M ALIVE!");
        NPCList nl = npcLists[type];
        if (nl != null) {
            n.name = nl.npcName;
            n.combatLevel = nl.combatLevel;
            n.maxHP = nl.maxHP;
            n.currentHP = n.maxHP;
            n.maxHit = nl.maxHit;
            n.atkType = nl.atkType;
            n.weakness = nl.weakness;
            n.respawnDelay = nl.respawnDelay;
            n.size = nl.size;
        }
        return index;
    }

    public static void newSmartNPC(int[] args) {
    }

}